{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Pandas 字符串提取和正则表达式\n",
    "\n",
    "本教程介绍Pandas中字符串提取和正则表达式的相关方法，包括extract、extractall、match、fullmatch和findall等。\n",
    "\n",
    "## 学习目标\n",
    "\n",
    "- 掌握`str.extract()`方法：提取匹配的第一个子串\n",
    "- 掌握`str.extractall()`方法：提取所有匹配的子串\n",
    "- 掌握`str.match()`方法：从开头匹配正则表达式\n",
    "- 掌握`str.fullmatch()`方法：完全匹配正则表达式\n",
    "- 掌握`str.findall()`方法：查找所有匹配\n",
    "- 了解正则表达式在Pandas中的应用\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 导入库\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 创建示例数据\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "示例数据：\n",
      "   姓名                    邮箱             电话                 身份证          文本  \\\n",
      "0  张三  zhang123@example.com   010-12345678  110101199001011234  价格：¥199.99   \n",
      "1  李四        li456@test.com   021-87654321  320101199002022345  价格：¥299.50   \n",
      "2  王五      wang789@demo.com  0755-11223344  440101199003033456  价格：¥399.00   \n",
      "3  赵六       zhao@sample.org   029-55667788  610101199004044567  价格：¥599.99   \n",
      "\n",
      "               混合文本          日期  \n",
      "0  订单号：ORD-2023-001  2023-01-15  \n",
      "1  订单号：ORD-2023-002  2023-02-20  \n",
      "2  订单号：ORD-2024-001  2024-03-25  \n",
      "3  订单号：ORD-2024-002  2024-04-30  \n"
     ]
    }
   ],
   "source": [
    "# 创建包含各种格式数据的DataFrame\n",
    "df = pd.DataFrame({\n",
    "    '姓名': ['张三', '李四', '王五', '赵六'],\n",
    "    '邮箱': ['zhang123@example.com', 'li456@test.com', 'wang789@demo.com', 'zhao@sample.org'],\n",
    "    '电话': ['010-12345678', '021-87654321', '0755-11223344', '029-55667788'],\n",
    "    '身份证': ['110101199001011234', '320101199002022345', '440101199003033456', '610101199004044567'],\n",
    "    '文本': ['价格：¥199.99', '价格：¥299.50', '价格：¥399.00', '价格：¥599.99'],\n",
    "    '混合文本': ['订单号：ORD-2023-001', '订单号：ORD-2023-002', '订单号：ORD-2024-001', '订单号：ORD-2024-002'],\n",
    "    '日期': ['2023-01-15', '2023-02-20', '2024-03-25', '2024-04-30']\n",
    "})\n",
    "\n",
    "print(\"示例数据：\")\n",
    "print(df)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. str.extract() - 提取匹配的第一个子串\n",
    "\n",
    "`str.extract()`方法使用正则表达式提取匹配的第一个子串，返回一个DataFrame。\n",
    "\n",
    "### 语法\n",
    "```python\n",
    "series.str.extract(pat, flags=0, expand=True)\n",
    "```\n",
    "\n",
    "### 参数\n",
    "- `pat`: 正则表达式模式（字符串或编译后的正则表达式对象）\n",
    "- `flags`: 正则表达式标志（如re.IGNORECASE）\n",
    "- `expand`: 是否返回DataFrame（True）或Series（False）\n",
    "\n",
    "### 特点\n",
    "- 如果正则表达式包含捕获组，每个捕获组会成为一列\n",
    "- 如果没有匹配，返回NaN\n",
    "- 只提取第一个匹配\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 提取单个捕获组\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取邮箱用户名：\n",
      "                     邮箱       用户名\n",
      "0  zhang123@example.com  zhang123\n",
      "1        li456@test.com     li456\n",
      "2      wang789@demo.com   wang789\n",
      "3       zhao@sample.org      zhao\n"
     ]
    }
   ],
   "source": [
    "# 从邮箱中提取用户名（@之前的部分）\n",
    "df['用户名'] = df['邮箱'].str.extract(r'([^@]+)@')\n",
    "\n",
    "print(\"提取邮箱用户名：\")\n",
    "print(df[['邮箱', '用户名']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 提取多个捕获组\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取电话区号和号码：\n",
      "              电话    区号        号码\n",
      "0   010-12345678   010  12345678\n",
      "1   021-87654321   021  87654321\n",
      "2  0755-11223344  0755  11223344\n",
      "3   029-55667788   029  55667788\n"
     ]
    }
   ],
   "source": [
    "# 从电话中提取区号和号码\n",
    "df[['区号', '号码']] = df['电话'].str.extract(r'(\\d{3,4})-(\\d{8})')\n",
    "\n",
    "print(\"提取电话区号和号码：\")\n",
    "print(df[['电话', '区号', '号码']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 提取价格\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取价格：\n",
      "           文本      价格\n",
      "0  价格：¥199.99  199.99\n",
      "1  价格：¥299.50  299.50\n",
      "2  价格：¥399.00  399.00\n",
      "3  价格：¥599.99  599.99\n"
     ]
    }
   ],
   "source": [
    "# 从文本中提取价格（数字部分）\n",
    "df['价格'] = df['文本'].str.extract(r'¥(\\d+\\.?\\d*)')\n",
    "\n",
    "print(\"提取价格：\")\n",
    "print(df[['文本', '价格']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 提取身份证信息\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取身份证出生日期：\n",
      "                  身份证    出生日期_格式化\n",
      "0  110101199001011234  1990-01-01\n",
      "1  320101199002022345  1990-02-02\n",
      "2  440101199003033456  1990-03-03\n",
      "3  610101199004044567  1990-04-04\n",
      "\n",
      "提取出生年份：\n",
      "                  身份证  出生年份\n",
      "0  110101199001011234  1990\n",
      "1  320101199002022345  1990\n",
      "2  440101199003033456  1990\n",
      "3  610101199004044567  1990\n"
     ]
    }
   ],
   "source": [
    "# 从身份证中提取出生日期（第7-14位）\n",
    "# extract()使用多个捕获组时返回多列DataFrame，需要分别提取\n",
    "df[['地区码', '年份', '月份', '日期']] = df['身份证'].str.extract(r'(\\d{6})(\\d{4})(\\d{2})(\\d{2})')\n",
    "# 格式化出生日期\n",
    "df['出生日期_格式化'] = df.apply(lambda x: f\"{x['年份']}-{x['月份']}-{x['日期']}\" if pd.notna(x['年份']) else None, axis=1)\n",
    "\n",
    "print(\"提取身份证出生日期：\")\n",
    "print(df[['身份证', '出生日期_格式化']])\n",
    "\n",
    "# 更简单的方式：直接提取日期部分\n",
    "df['出生年份'] = df['身份证'].str.extract(r'\\d{6}(\\d{4})')\n",
    "print(\"\\n提取出生年份：\")\n",
    "print(df[['身份证', '出生年份']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.5 使用命名捕获组\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "使用命名捕获组提取订单信息：\n",
      "   year  num\n",
      "0  2023  001\n",
      "1  2023  002\n",
      "2  2024  001\n",
      "3  2024  002\n"
     ]
    }
   ],
   "source": [
    "# 使用命名捕获组，列名更清晰\n",
    "result = df['混合文本'].str.extract(r'ORD-(?P<year>\\d{4})-(?P<num>\\d{3})')\n",
    "\n",
    "print(\"使用命名捕获组提取订单信息：\")\n",
    "print(result)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.6 expand参数的使用\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "expand=False 返回Series：\n",
      "<class 'pandas.core.series.Series'>\n",
      "0    zhang123\n",
      "1       li456\n",
      "2     wang789\n",
      "3        zhao\n",
      "Name: 邮箱, dtype: object\n",
      "\n",
      "expand=True 返回DataFrame（默认）：\n",
      "<class 'pandas.core.frame.DataFrame'>\n",
      "          0\n",
      "0  zhang123\n",
      "1     li456\n",
      "2   wang789\n",
      "3      zhao\n"
     ]
    }
   ],
   "source": [
    "# expand=False 返回Series而不是DataFrame\n",
    "result_series = df['邮箱'].str.extract(r'([^@]+)@', expand=False)\n",
    "\n",
    "print(\"expand=False 返回Series：\")\n",
    "print(type(result_series))\n",
    "print(result_series)\n",
    "\n",
    "print(\"\\nexpand=True 返回DataFrame（默认）：\")\n",
    "result_df = df['邮箱'].str.extract(r'([^@]+)@', expand=True)\n",
    "print(type(result_df))\n",
    "print(result_df)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. str.extractall() - 提取所有匹配的子串\n",
    "\n",
    "`str.extractall()`方法提取所有匹配的子串，返回一个MultiIndex DataFrame。\n",
    "\n",
    "### 语法\n",
    "```python\n",
    "series.str.extractall(pat, flags=0)\n",
    "```\n",
    "\n",
    "### 参数\n",
    "- `pat`: 正则表达式模式\n",
    "- `flags`: 正则表达式标志\n",
    "\n",
    "### 特点\n",
    "- 返回所有匹配，而不仅仅是第一个\n",
    "- 返回的DataFrame具有MultiIndex（原始索引 + 匹配序号）\n",
    "- 每个捕获组成为一列\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1 提取所有数字\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取所有数字：\n",
      "              0\n",
      "  match        \n",
      "0 0      199.99\n",
      "  1          50\n",
      "1 0      299.50\n",
      "  1          30\n",
      "2 0      399.00\n",
      "\n",
      "MultiIndex结构：\n",
      "MultiIndex([(0, 0),\n",
      "            (0, 1),\n",
      "            (1, 0),\n",
      "            (1, 1),\n",
      "            (2, 0)],\n",
      "           names=[None, 'match'])\n"
     ]
    }
   ],
   "source": [
    "# 从文本中提取所有数字\n",
    "texts = pd.Series(['价格：¥199.99，折扣：50%', '价格：¥299.50，折扣：30%', '价格：¥399.00'])\n",
    "df_texts = pd.DataFrame({'文本': texts})\n",
    "\n",
    "# 提取所有数字（包括小数）\n",
    "all_numbers = df_texts['文本'].str.extractall(r'(\\d+\\.?\\d*)')\n",
    "\n",
    "print(\"提取所有数字：\")\n",
    "print(all_numbers)\n",
    "print(\"\\nMultiIndex结构：\")\n",
    "print(all_numbers.index)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 提取所有邮箱地址\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取所有邮箱：\n",
      "                         0\n",
      "  match                   \n",
      "0 0      zhang@example.com\n",
      "  1            li@test.com\n",
      "1 0          wang@demo.com\n",
      "  1        zhao@sample.org\n",
      "2 0      admin@company.com\n",
      "\n",
      "重置索引以便查看：\n",
      "   level_0  match                  0\n",
      "0        0      0  zhang@example.com\n",
      "1        0      1        li@test.com\n",
      "2        1      0      wang@demo.com\n",
      "3        1      1    zhao@sample.org\n",
      "4        2      0  admin@company.com\n"
     ]
    }
   ],
   "source": [
    "# 从文本中提取所有邮箱地址\n",
    "texts_with_emails = pd.Series([\n",
    "    '联系邮箱：zhang@example.com 或 li@test.com',\n",
    "    '邮箱：wang@demo.com，备用：zhao@sample.org',\n",
    "    'email: admin@company.com'\n",
    "])\n",
    "df_emails = pd.DataFrame({'文本': texts_with_emails})\n",
    "\n",
    "# 提取所有邮箱\n",
    "all_emails = df_emails['文本'].str.extractall(r'([\\w.-]+@[\\w.-]+\\.[\\w]+)')\n",
    "\n",
    "print(\"提取所有邮箱：\")\n",
    "print(all_emails)\n",
    "print(\"\\n重置索引以便查看：\")\n",
    "print(all_emails.reset_index())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3 提取多个捕获组的所有匹配\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取所有日期：\n",
      "            年   月   日\n",
      "  match              \n",
      "0 0      2023  01  15\n",
      "  1      2023  02  20\n",
      "1 0      2024  03  25\n",
      "  1      2024  04  30\n",
      "2 0      2023  12  31\n",
      "\n",
      "重置索引：\n",
      "   level_0  match     年   月   日\n",
      "0        0      0  2023  01  15\n",
      "1        0      1  2023  02  20\n",
      "2        1      0  2024  03  25\n",
      "3        1      1  2024  04  30\n",
      "4        2      0  2023  12  31\n"
     ]
    }
   ],
   "source": [
    "# 从文本中提取所有日期格式\n",
    "texts_with_dates = pd.Series([\n",
    "    '会议时间：2023-01-15 和 2023-02-20',\n",
    "    '日期：2024-03-25，2024-04-30',\n",
    "    '2023-12-31'\n",
    "])\n",
    "df_dates = pd.DataFrame({'文本': texts_with_dates})\n",
    "\n",
    "# 提取所有日期（年、月、日分别提取）\n",
    "all_dates = df_dates['文本'].str.extractall(r'(\\d{4})-(\\d{2})-(\\d{2})')\n",
    "all_dates.columns = ['年', '月', '日']\n",
    "\n",
    "print(\"提取所有日期：\")\n",
    "print(all_dates)\n",
    "print(\"\\n重置索引：\")\n",
    "print(all_dates.reset_index())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.4 extractall与extract的区别\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "extract() - 只提取第一个匹配：\n",
      "        0\n",
      "0  199.99\n",
      "\n",
      "extractall() - 提取所有匹配：\n",
      "              0\n",
      "  match        \n",
      "0 0      199.99\n",
      "  1          20\n"
     ]
    }
   ],
   "source": [
    "# 对比extract和extractall\n",
    "text = pd.Series(['价格：¥199.99，折扣：50%，优惠：¥20'])\n",
    "df_compare = pd.DataFrame({'文本': text})\n",
    "\n",
    "# extract只提取第一个匹配\n",
    "first_match = df_compare['文本'].str.extract(r'¥(\\d+\\.?\\d*)')\n",
    "print(\"extract() - 只提取第一个匹配：\")\n",
    "print(first_match)\n",
    "\n",
    "# extractall提取所有匹配\n",
    "all_matches = df_compare['文本'].str.extractall(r'¥(\\d+\\.?\\d*)')\n",
    "print(\"\\nextractall() - 提取所有匹配：\")\n",
    "print(all_matches)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. str.match() - 从开头匹配正则表达式\n",
    "\n",
    "`str.match()`方法检查字符串是否从开头匹配正则表达式，返回布尔值Series。\n",
    "\n",
    "### 语法\n",
    "```python\n",
    "series.str.match(pat, case=True, flags=0, na=nan)\n",
    "```\n",
    "\n",
    "### 参数\n",
    "- `pat`: 正则表达式模式\n",
    "- `case`: 是否区分大小写（True区分，False不区分）\n",
    "- `flags`: 正则表达式标志\n",
    "- `na`: 缺失值的处理方式\n",
    "\n",
    "### 特点\n",
    "- 只检查字符串开头是否匹配\n",
    "- 返回布尔值Series\n",
    "- 常用于数据验证和过滤\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1 检查字符串开头\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "检查字符串开头：\n",
      "                  邮箱   以z开头  以zhang开头\n",
      "0  zhang@example.com   True      True\n",
      "1        li@test.com  False     False\n",
      "2  admin@example.com  False     False\n",
      "3      user@demo.com  False     False\n"
     ]
    }
   ],
   "source": [
    "# 检查邮箱是否以特定字符开头\n",
    "emails = pd.Series(['zhang@example.com', 'li@test.com', 'admin@example.com', 'user@demo.com'])\n",
    "df_emails = pd.DataFrame({'邮箱': emails})\n",
    "\n",
    "# 检查是否以z开头（不区分大小写）\n",
    "df_emails['以z开头'] = df_emails['邮箱'].str.match(r'z', case=False)\n",
    "df_emails['以zhang开头'] = df_emails['邮箱'].str.match(r'zhang')\n",
    "\n",
    "print(\"检查字符串开头：\")\n",
    "print(df_emails)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 验证数据格式\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "验证电话号码格式：\n",
      "              电话   格式正确\n",
      "0   010-12345678   True\n",
      "1   021-87654321   True\n",
      "2  0755-11223344   True\n",
      "3     123-456789   True\n",
      "4    01012345678  False\n"
     ]
    }
   ],
   "source": [
    "# 验证电话号码格式（以010、021等开头）\n",
    "phones = pd.Series(['010-12345678', '021-87654321', '0755-11223344', '123-456789', '01012345678'])\n",
    "df_phones = pd.DataFrame({'电话': phones})\n",
    "\n",
    "# 检查是否以正确的区号格式开头（3-4位数字+横线）\n",
    "df_phones['格式正确'] = df_phones['电话'].str.match(r'\\d{3,4}-')\n",
    "\n",
    "print(\"验证电话号码格式：\")\n",
    "print(df_phones)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3 过滤数据\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "所有订单：\n",
      "            订单号\n",
      "0  ORD-2023-001\n",
      "1  ORD-2023-002\n",
      "2  INV-2023-001\n",
      "3  ORD-2024-001\n",
      "4   PO-2023-001\n",
      "\n",
      "以ORD开头的订单：\n",
      "            订单号\n",
      "0  ORD-2023-001\n",
      "1  ORD-2023-002\n",
      "3  ORD-2024-001\n"
     ]
    }
   ],
   "source": [
    "# 过滤出以特定前缀开头的订单\n",
    "orders = pd.Series(['ORD-2023-001', 'ORD-2023-002', 'INV-2023-001', 'ORD-2024-001', 'PO-2023-001'])\n",
    "df_orders = pd.DataFrame({'订单号': orders})\n",
    "\n",
    "# 过滤出以ORD开头的订单\n",
    "ord_orders = df_orders[df_orders['订单号'].str.match(r'ORD-')]\n",
    "\n",
    "print(\"所有订单：\")\n",
    "print(df_orders)\n",
    "print(\"\\n以ORD开头的订单：\")\n",
    "print(ord_orders)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.4 大小写敏感控制\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "大小写敏感控制：\n",
      "      文本  匹配Hello_区分大小写  匹配Hello_不区分大小写\n",
      "0  Hello           True            True\n",
      "1  hello          False            True\n",
      "2  HELLO          False            True\n",
      "3  World          False           False\n",
      "4  world          False           False\n"
     ]
    }
   ],
   "source": [
    "# case参数控制大小写敏感性\n",
    "texts = pd.Series(['Hello', 'hello', 'HELLO', 'World', 'world'])\n",
    "df_texts = pd.DataFrame({'文本': texts})\n",
    "\n",
    "# 区分大小写（默认）\n",
    "df_texts['匹配Hello_区分大小写'] = df_texts['文本'].str.match(r'Hello', case=True)\n",
    "\n",
    "# 不区分大小写\n",
    "df_texts['匹配Hello_不区分大小写'] = df_texts['文本'].str.match(r'Hello', case=False)\n",
    "\n",
    "print(\"大小写敏感控制：\")\n",
    "print(df_texts)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. str.fullmatch() - 完全匹配正则表达式\n",
    "\n",
    "`str.fullmatch()`方法检查字符串是否完全匹配正则表达式，返回布尔值Series。\n",
    "\n",
    "### 语法\n",
    "```python\n",
    "series.str.fullmatch(pat, case=True, flags=0, na=nan)\n",
    "```\n",
    "\n",
    "### 参数\n",
    "- `pat`: 正则表达式模式\n",
    "- `case`: 是否区分大小写\n",
    "- `flags`: 正则表达式标志\n",
    "- `na`: 缺失值的处理方式\n",
    "\n",
    "### 特点\n",
    "- 整个字符串必须完全匹配正则表达式\n",
    "- 返回布尔值Series\n",
    "- 常用于严格的数据验证\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1 验证完整格式\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "验证邮箱格式：\n",
      "                  邮箱   格式有效\n",
      "0  zhang@example.com   True\n",
      "1        li@test.com   True\n",
      "2      invalid-email  False\n",
      "3        user@domain  False\n",
      "4     test@valid.com   True\n"
     ]
    }
   ],
   "source": [
    "# 验证邮箱格式（必须完全匹配邮箱格式）\n",
    "emails = pd.Series([\n",
    "    'zhang@example.com',\n",
    "    'li@test.com',\n",
    "    'invalid-email',\n",
    "    'user@domain',\n",
    "    'test@valid.com'\n",
    "])\n",
    "df_emails = pd.DataFrame({'邮箱': emails})\n",
    "\n",
    "# 验证是否为有效的邮箱格式\n",
    "df_emails['格式有效'] = df_emails['邮箱'].str.fullmatch(r'[\\w.-]+@[\\w.-]+\\.[\\w]+')\n",
    "\n",
    "print(\"验证邮箱格式：\")\n",
    "print(df_emails)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 验证身份证号格式\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "验证身份证号格式：\n",
      "                   身份证  格式正确_18位数字  格式正确_可含X\n",
      "0   110101199001011234        True      True\n",
      "1   320101199002022345        True      True\n",
      "2    12345678901234567       False     False\n",
      "3  1234567890123456789       False     False\n",
      "4   11010119900101123X       False      True\n"
     ]
    }
   ],
   "source": [
    "# 验证身份证号格式（18位数字）\n",
    "ids = pd.Series([\n",
    "    '110101199001011234',\n",
    "    '320101199002022345',\n",
    "    '12345678901234567',  # 17位\n",
    "    '1234567890123456789',  # 19位\n",
    "    '11010119900101123X'  # 最后一位是X\n",
    "])\n",
    "df_ids = pd.DataFrame({'身份证': ids})\n",
    "\n",
    "# 验证18位数字身份证\n",
    "df_ids['格式正确_18位数字'] = df_ids['身份证'].str.fullmatch(r'\\d{18}')\n",
    "\n",
    "# 验证18位（最后一位可以是X）\n",
    "df_ids['格式正确_可含X'] = df_ids['身份证'].str.fullmatch(r'\\d{17}[\\dX]')\n",
    "\n",
    "print(\"验证身份证号格式：\")\n",
    "print(df_ids)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 验证日期格式\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "验证日期格式：\n",
      "           日期   格式正确\n",
      "0  2023-01-15   True\n",
      "1  2023-02-20   True\n",
      "2    23-01-15  False\n",
      "3  2023/01/15  False\n",
      "4    2023-1-5  False\n"
     ]
    }
   ],
   "source": [
    "# 验证日期格式（YYYY-MM-DD）\n",
    "dates = pd.Series([\n",
    "    '2023-01-15',\n",
    "    '2023-02-20',\n",
    "    '23-01-15',  # 年份不完整\n",
    "    '2023/01/15',  # 分隔符错误\n",
    "    '2023-1-5'  # 月份和日期不完整\n",
    "])\n",
    "df_dates = pd.DataFrame({'日期': dates})\n",
    "\n",
    "# 验证严格的日期格式\n",
    "df_dates['格式正确'] = df_dates['日期'].str.fullmatch(r'\\d{4}-\\d{2}-\\d{2}')\n",
    "\n",
    "print(\"验证日期格式：\")\n",
    "print(df_dates)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4 match与fullmatch的区别\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "match vs fullmatch 对比：\n",
      "            文本  match_Hello  fullmatch_Hello\n",
      "0        Hello         True             True\n",
      "1  Hello World         True            False\n",
      "2     Hello123         True            False\n"
     ]
    }
   ],
   "source": [
    "# 对比match和fullmatch\n",
    "texts = pd.Series(['Hello', 'Hello World', 'Hello123'])\n",
    "df_compare = pd.DataFrame({'文本': texts})\n",
    "\n",
    "# match：只检查开头\n",
    "df_compare['match_Hello'] = df_compare['文本'].str.match(r'Hello')\n",
    "\n",
    "# fullmatch：必须完全匹配\n",
    "df_compare['fullmatch_Hello'] = df_compare['文本'].str.fullmatch(r'Hello')\n",
    "\n",
    "print(\"match vs fullmatch 对比：\")\n",
    "print(df_compare)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. str.findall() - 查找所有匹配\n",
    "\n",
    "`str.findall()`方法查找所有匹配的子串，返回一个Series，每个元素是一个匹配列表。\n",
    "\n",
    "### 语法\n",
    "```python\n",
    "series.str.findall(pat, flags=0)\n",
    "```\n",
    "\n",
    "### 参数\n",
    "- `pat`: 正则表达式模式\n",
    "- `flags`: 正则表达式标志\n",
    "\n",
    "### 特点\n",
    "- 返回所有匹配，每个元素是一个列表\n",
    "- 如果没有匹配，返回空列表\n",
    "- 如果正则表达式有捕获组，返回捕获组的内容\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1 查找所有数字\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "查找所有数字：\n",
      "                         文本              所有数字\n",
      "0         价格：¥199.99，折扣：50%      [199.99, 50]\n",
      "1  价格：¥299.50，折扣：30%，优惠：¥20  [299.50, 30, 20]\n",
      "2                价格：¥399.00          [399.00]\n"
     ]
    }
   ],
   "source": [
    "# 从文本中查找所有数字\n",
    "texts = pd.Series([\n",
    "    '价格：¥199.99，折扣：50%',\n",
    "    '价格：¥299.50，折扣：30%，优惠：¥20',\n",
    "    '价格：¥399.00'\n",
    "])\n",
    "df_texts = pd.DataFrame({'文本': texts})\n",
    "\n",
    "# 查找所有数字（包括小数）\n",
    "df_texts['所有数字'] = df_texts['文本'].str.findall(r'\\d+\\.?\\d*')\n",
    "\n",
    "print(\"查找所有数字：\")\n",
    "print(df_texts)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2 查找所有邮箱\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "查找所有邮箱：\n",
      "                                     文本                              所有邮箱\n",
      "0  联系邮箱：zhang@example.com 或 li@test.com  [zhang@example.com, li@test.com]\n",
      "1   邮箱：wang@demo.com，备用：zhao@sample.org  [wang@demo.com, zhao@sample.org]\n",
      "2              email: admin@company.com               [admin@company.com]\n"
     ]
    }
   ],
   "source": [
    "# 从文本中查找所有邮箱\n",
    "texts_with_emails = pd.Series([\n",
    "    '联系邮箱：zhang@example.com 或 li@test.com',\n",
    "    '邮箱：wang@demo.com，备用：zhao@sample.org',\n",
    "    'email: admin@company.com'\n",
    "])\n",
    "df_emails = pd.DataFrame({'文本': texts_with_emails})\n",
    "\n",
    "# 查找所有邮箱\n",
    "df_emails['所有邮箱'] = df_emails['文本'].str.findall(r'[\\w.-]+@[\\w.-]+\\.[\\w]+')\n",
    "\n",
    "print(\"查找所有邮箱：\")\n",
    "print(df_emails)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.3 使用捕获组\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "使用捕获组：\n",
      "                            文本  订单号列表      价格列表\n",
      "0  订单号：ORD-2023-001，金额：¥199.99  [ORD]  [199.99]\n",
      "1  订单号：ORD-2023-002，金额：¥299.50  [ORD]  [299.50]\n",
      "2  订单号：INV-2024-001，金额：¥399.00  [INV]  [399.00]\n"
     ]
    }
   ],
   "source": [
    "# 使用捕获组提取特定部分\n",
    "texts = pd.Series([\n",
    "    '订单号：ORD-2023-001，金额：¥199.99',\n",
    "    '订单号：ORD-2023-002，金额：¥299.50',\n",
    "    '订单号：INV-2024-001，金额：¥399.00'\n",
    "])\n",
    "df_texts = pd.DataFrame({'文本': texts})\n",
    "\n",
    "# 提取所有订单号（使用捕获组）\n",
    "df_texts['订单号列表'] = df_texts['文本'].str.findall(r'(ORD|INV)-\\d{4}-\\d{3}')\n",
    "\n",
    "# 提取所有价格\n",
    "df_texts['价格列表'] = df_texts['文本'].str.findall(r'¥(\\d+\\.?\\d*)')\n",
    "\n",
    "print(\"使用捕获组：\")\n",
    "print(df_texts)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.4 处理findall结果\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "处理findall结果：\n",
      "                  文本      价格列表   第一个价格    价格总和  价格数量\n",
      "0  价格：¥199.99，折扣：50%  [199.99]  199.99  199.99     1\n",
      "1         价格：¥299.50  [299.50]  299.50  299.50     1\n",
      "2              无价格信息        []    None    0.00     0\n"
     ]
    }
   ],
   "source": [
    "# 处理findall返回的列表\n",
    "texts = pd.Series(['价格：¥199.99，折扣：50%', '价格：¥299.50', '无价格信息'])\n",
    "df_texts = pd.DataFrame({'文本': texts})\n",
    "\n",
    "# 查找所有价格\n",
    "df_texts['价格列表'] = df_texts['文本'].str.findall(r'¥(\\d+\\.?\\d*)')\n",
    "\n",
    "# 获取第一个价格\n",
    "df_texts['第一个价格'] = df_texts['价格列表'].apply(lambda x: x[0] if x else None)\n",
    "\n",
    "# 获取所有价格的总和（转换为数字）\n",
    "df_texts['价格总和'] = df_texts['价格列表'].apply(lambda x: sum(float(p) for p in x) if x else 0)\n",
    "\n",
    "# 获取价格数量\n",
    "df_texts['价格数量'] = df_texts['价格列表'].apply(len)\n",
    "\n",
    "print(\"处理findall结果：\")\n",
    "print(df_texts)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.5 findall与extractall的区别\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "findall() 结果（Series of lists）：\n",
      "0    [199.99, 20]\n",
      "Name: 文本, dtype: object\n",
      "类型：<class 'pandas.core.series.Series'>\n",
      "\n",
      "extractall() 结果（DataFrame with MultiIndex）：\n",
      "              0\n",
      "  match        \n",
      "0 0      199.99\n",
      "  1          20\n",
      "类型：<class 'pandas.core.frame.DataFrame'>\n"
     ]
    }
   ],
   "source": [
    "# 对比findall和extractall\n",
    "text = pd.Series(['价格：¥199.99，折扣：50%，优惠：¥20'])\n",
    "df_compare = pd.DataFrame({'文本': text})\n",
    "\n",
    "# findall返回Series，每个元素是列表\n",
    "findall_result = df_compare['文本'].str.findall(r'¥(\\d+\\.?\\d*)')\n",
    "print(\"findall() 结果（Series of lists）：\")\n",
    "print(findall_result)\n",
    "print(f\"类型：{type(findall_result)}\")\n",
    "\n",
    "# extractall返回DataFrame with MultiIndex\n",
    "extractall_result = df_compare['文本'].str.extractall(r'¥(\\d+\\.?\\d*)')\n",
    "print(\"\\nextractall() 结果（DataFrame with MultiIndex）：\")\n",
    "print(extractall_result)\n",
    "print(f\"类型：{type(extractall_result)}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. 实际应用场景\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.1 数据清洗：提取结构化信息\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "从非结构化文本提取结构化信息：\n",
      "                                           信息  姓名             电话  \\\n",
      "0  姓名：张三，电话：010-12345678，邮箱：zhang@example.com  张三   010-12345678   \n",
      "1        姓名：李四，电话：021-87654321，邮箱：li@test.com  李四   021-87654321   \n",
      "2     姓名：王五，电话：0755-11223344，邮箱：wang@demo.com  王五  0755-11223344   \n",
      "\n",
      "                  邮箱  \n",
      "0  zhang@example.com  \n",
      "1        li@test.com  \n",
      "2      wang@demo.com  \n"
     ]
    }
   ],
   "source": [
    "# 从非结构化文本中提取结构化信息\n",
    "raw_data = pd.DataFrame({\n",
    "    '信息': [\n",
    "        '姓名：张三，电话：010-12345678，邮箱：zhang@example.com',\n",
    "        '姓名：李四，电话：021-87654321，邮箱：li@test.com',\n",
    "        '姓名：王五，电话：0755-11223344，邮箱：wang@demo.com'\n",
    "    ]\n",
    "})\n",
    "\n",
    "# 提取姓名、电话、邮箱\n",
    "raw_data['姓名'] = raw_data['信息'].str.extract(r'姓名：(\\w+)')\n",
    "raw_data['电话'] = raw_data['信息'].str.extract(r'电话：(\\d{3,4}-\\d{8})')\n",
    "raw_data['邮箱'] = raw_data['信息'].str.extract(r'邮箱：([\\w.-]+@[\\w.-]+\\.[\\w]+)')\n",
    "\n",
    "print(\"从非结构化文本提取结构化信息：\")\n",
    "print(raw_data[['信息', '姓名', '电话', '邮箱']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.2 数据验证：检查数据质量\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据验证结果：\n",
      "                  邮箱             电话                 身份证   邮箱有效   电话有效  身份证有效\n",
      "0  zhang@example.com   010-12345678  110101199001011234   True   True   True\n",
      "1      invalid-email   021-87654321   12345678901234567  False   True  False\n",
      "2        li@test.com        123-456  320101199002022345   True  False   True\n",
      "3        user@domain  0755-11223344             invalid  False   True  False\n",
      "\n",
      "所有数据都有效的记录：\n",
      "                  邮箱            电话                 身份证  邮箱有效  电话有效  身份证有效\n",
      "0  zhang@example.com  010-12345678  110101199001011234  True  True   True\n"
     ]
    }
   ],
   "source": [
    "# 验证数据质量\n",
    "user_data = pd.DataFrame({\n",
    "    '邮箱': ['zhang@example.com', 'invalid-email', 'li@test.com', 'user@domain'],\n",
    "    '电话': ['010-12345678', '021-87654321', '123-456', '0755-11223344'],\n",
    "    '身份证': ['110101199001011234', '12345678901234567', '320101199002022345', 'invalid']\n",
    "})\n",
    "\n",
    "# 验证各项数据\n",
    "user_data['邮箱有效'] = user_data['邮箱'].str.fullmatch(r'[\\w.-]+@[\\w.-]+\\.[\\w]+')\n",
    "user_data['电话有效'] = user_data['电话'].str.fullmatch(r'\\d{3,4}-\\d{8}')\n",
    "user_data['身份证有效'] = user_data['身份证'].str.fullmatch(r'\\d{17}[\\dX]')\n",
    "\n",
    "print(\"数据验证结果：\")\n",
    "print(user_data)\n",
    "\n",
    "# 找出所有数据都有效的记录\n",
    "valid_records = user_data[user_data['邮箱有效'] & user_data['电话有效'] & user_data['身份证有效']]\n",
    "print(\"\\n所有数据都有效的记录：\")\n",
    "print(valid_records)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.3 日志分析：提取关键信息\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "从日志提取信息：\n",
      "                                                  日志          日期        时间  \\\n",
      "0     2023-01-15 10:30:45 [INFO] User login: user123  2023-01-15  10:30:45   \n",
      "1  2023-01-15 10:31:20 [ERROR] Database connectio...  2023-01-15  10:31:20   \n",
      "2  2023-01-15 10:32:10 [INFO] Order created: ORD-...  2023-01-15  10:32:10   \n",
      "3       2023-01-15 10:33:05 [WARN] Low memory: 512MB  2023-01-15  10:33:05   \n",
      "\n",
      "      级别                           消息  \n",
      "0   INFO          User login: user123  \n",
      "1  ERROR   Database connection failed  \n",
      "2   INFO  Order created: ORD-2023-001  \n",
      "3   WARN            Low memory: 512MB  \n",
      "\n",
      "提取的订单号：\n",
      "                                                  日志             订单号\n",
      "0     2023-01-15 10:30:45 [INFO] User login: user123              []\n",
      "1  2023-01-15 10:31:20 [ERROR] Database connectio...              []\n",
      "2  2023-01-15 10:32:10 [INFO] Order created: ORD-...  [ORD-2023-001]\n",
      "3       2023-01-15 10:33:05 [WARN] Low memory: 512MB              []\n"
     ]
    }
   ],
   "source": [
    "# 从日志中提取关键信息\n",
    "logs = pd.DataFrame({\n",
    "    '日志': [\n",
    "        '2023-01-15 10:30:45 [INFO] User login: user123',\n",
    "        '2023-01-15 10:31:20 [ERROR] Database connection failed',\n",
    "        '2023-01-15 10:32:10 [INFO] Order created: ORD-2023-001',\n",
    "        '2023-01-15 10:33:05 [WARN] Low memory: 512MB'\n",
    "    ]\n",
    "})\n",
    "\n",
    "# 提取时间戳、日志级别、消息\n",
    "logs[['日期', '时间']] = logs['日志'].str.extract(r'(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2})')\n",
    "logs['级别'] = logs['日志'].str.extract(r'\\[(\\w+)\\]')\n",
    "logs['消息'] = logs['日志'].str.extract(r'\\[\\w+\\] (.+)')\n",
    "\n",
    "print(\"从日志提取信息：\")\n",
    "print(logs[['日志', '日期', '时间', '级别', '消息']])\n",
    "\n",
    "# 提取所有订单号\n",
    "logs['订单号'] = logs['日志'].str.findall(r'ORD-\\d{4}-\\d{3}')\n",
    "print(\"\\n提取的订单号：\")\n",
    "print(logs[['日志', '订单号']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.4 文本挖掘：提取关键词\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取关键词：\n",
      "                          评论        价格   满意度\n",
      "0  这个产品价格¥199.99，质量很好，推荐度90%  [199.99]  [90]\n",
      "1  价格¥299.50有点贵，但质量不错，满意度85%  [299.50]  [85]\n",
      "2     性价比高，¥399.00值得购买，推荐95%  [399.00]  [95]\n",
      "\n",
      "计算平均值：\n",
      "                          评论    平均价格  平均满意度\n",
      "0  这个产品价格¥199.99，质量很好，推荐度90%  199.99   90.0\n",
      "1  价格¥299.50有点贵，但质量不错，满意度85%  299.50   85.0\n",
      "2     性价比高，¥399.00值得购买，推荐95%  399.00   95.0\n"
     ]
    }
   ],
   "source": [
    "# 从文本中提取关键词（如价格、百分比等）\n",
    "product_reviews = pd.DataFrame({\n",
    "    '评论': [\n",
    "        '这个产品价格¥199.99，质量很好，推荐度90%',\n",
    "        '价格¥299.50有点贵，但质量不错，满意度85%',\n",
    "        '性价比高，¥399.00值得购买，推荐95%'\n",
    "    ]\n",
    "})\n",
    "\n",
    "# 提取所有价格\n",
    "product_reviews['价格'] = product_reviews['评论'].str.findall(r'¥(\\d+\\.?\\d*)')\n",
    "\n",
    "# 提取所有百分比\n",
    "product_reviews['满意度'] = product_reviews['评论'].str.findall(r'(\\d+)%')\n",
    "\n",
    "print(\"提取关键词：\")\n",
    "print(product_reviews)\n",
    "\n",
    "# 计算平均价格和平均满意度\n",
    "product_reviews['平均价格'] = product_reviews['价格'].apply(\n",
    "    lambda x: sum(float(p) for p in x) / len(x) if x else None\n",
    ")\n",
    "product_reviews['平均满意度'] = product_reviews['满意度'].apply(\n",
    "    lambda x: sum(int(s) for s in x) / len(x) if x else None\n",
    ")\n",
    "\n",
    "print(\"\\n计算平均值：\")\n",
    "print(product_reviews[['评论', '平均价格', '平均满意度']])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. 方法对比总结\n",
    "\n",
    "| 方法 | 功能 | 返回值 | 匹配数量 | 适用场景 |\n",
    "|------|------|--------|---------|---------|\n",
    "| `str.extract()` | 提取第一个匹配 | DataFrame/Series | 第一个 | 提取单个匹配的子串 |\n",
    "| `str.extractall()` | 提取所有匹配 | DataFrame (MultiIndex) | 所有 | 提取所有匹配，需要结构化结果 |\n",
    "| `str.match()` | 检查开头匹配 | 布尔值Series | 第一个 | 验证数据格式、过滤数据 |\n",
    "| `str.fullmatch()` | 检查完全匹配 | 布尔值Series | 完全匹配 | 严格验证数据格式 |\n",
    "| `str.findall()` | 查找所有匹配 | Series of lists | 所有 | 查找所有匹配，需要列表形式 |\n",
    "\n",
    "### 选择建议\n",
    "\n",
    "- **提取单个匹配**: 使用 `extract()` - 返回DataFrame，便于处理\n",
    "- **提取所有匹配（结构化）**: 使用 `extractall()` - 返回DataFrame with MultiIndex\n",
    "- **提取所有匹配（列表形式）**: 使用 `findall()` - 返回Series of lists，便于进一步处理\n",
    "- **验证开头**: 使用 `match()` - 检查字符串是否以特定模式开头\n",
    "- **严格验证**: 使用 `fullmatch()` - 检查整个字符串是否完全匹配\n",
    "\n",
    "### 性能考虑\n",
    "\n",
    "- `match()` 和 `fullmatch()` 性能最好（只返回布尔值）\n",
    "- `extract()` 性能中等（需要构建DataFrame）\n",
    "- `extractall()` 和 `findall()` 性能相对较慢（需要处理所有匹配）\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. 总结\n",
    "\n",
    "### 关键要点\n",
    "\n",
    "1. **extract()**: 提取第一个匹配，返回DataFrame，适合提取单个子串\n",
    "2. **extractall()**: 提取所有匹配，返回MultiIndex DataFrame，适合提取多个匹配并保持结构\n",
    "3. **match()**: 检查开头匹配，返回布尔值，适合数据验证和过滤\n",
    "4. **fullmatch()**: 检查完全匹配，返回布尔值，适合严格的数据格式验证\n",
    "5. **findall()**: 查找所有匹配，返回列表，适合提取多个匹配并需要列表形式\n",
    "\n",
    "### 使用建议\n",
    "\n",
    "- **数据提取**: 根据需求选择extract()或extractall()或findall()\n",
    "- **数据验证**: 使用match()或fullmatch()进行格式验证\n",
    "- **正则表达式**: 熟练掌握正则表达式语法，合理使用捕获组\n",
    "- **性能优化**: 对于大数据集，优先使用match()和fullmatch()进行过滤\n",
    "- **链式调用**: 可以与其他字符串方法链式调用，提高代码可读性\n",
    "\n",
    "### 常见正则表达式模式\n",
    "\n",
    "- `\\d+`: 一个或多个数字\n",
    "- `\\d{4}`: 恰好4个数字\n",
    "- `[\\w.-]+`: 一个或多个字母、数字、点或横线\n",
    "- `(\\d{4})-(\\d{2})-(\\d{2})`: 日期格式（年-月-日）\n",
    "- `[^@]+`: 一个或多个非@字符\n",
    "- `\\d+\\.?\\d*`: 数字（可能包含小数点）\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "ml311",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
